package com.dev.ac.util;


import com.deepoove.poi.xwpf.XWPFParagraphWrapper;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.util.Units;
import org.apache.poi.xwpf.usermodel.*;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.nodes.Node;
import org.jsoup.nodes.TextNode;
import org.jsoup.select.Elements;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.*;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLDecoder;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

/**
 * @Author 付
 * @Date 2024-04-19 16:44
 * @Version 1.0
 * @description html转word工具类
 * @since 1.0
 */
public class HtmlConvertWordUtil {
	/**
	 * html转word的main方法
	 *
	 * @param html 需要转换的html
	 */
	public static XWPFDocument htmlConvertWord(String html) {
		XWPFDocument doc = new XWPFDocument();
		// 通过Jsoup格式化html
		Document parse = Jsoup.parse(html);
		// 获取html中所有的标签元素
		Elements es = parse.body().getAllElements();
		// 筛选出所有的父级标签  限于p、h1、h2、h3、table
		List<Element> tag1 = es.stream().filter(x -> "p".equals(x.tagName()) || "h1".equals(x.tagName()) || "h2".equals(x.tagName()) || "h3".equals(x.tagName()) || "table".equals(x.tagName())).collect(Collectors.toList());
		// 循环每个父标签 ，每个父标签是一个段落，将父标签下的内容以及子标签对应的内容放到父标签段落中
		for (Element e : tag1) {
			//创建段落）
			createXWPFParagraph(doc, e);
		}
		return doc;
	}


	/**
	 * 构建段落
	 *
	 * @param docxDocument
	 * @param e
	 */
	public static void createXWPFParagraph(XWPFDocument docxDocument, Element e) {
		// 创建段落
		XWPFParagraph paragraph = docxDocument.createParagraph();
		// 存放样式
		List<String> allStyles = new ArrayList<>();
		// 创建内容并设置样式
		createXWPFRun(docxDocument, paragraph, e, allStyles);
	}

	/**
	 * 创建段落内容
	 *
	 * @param docxDocument 目标文档
	 * @param paragraph    段落
	 * @param e            html中的元素
	 * @param allStyles    样式
	 */
	public static void createXWPFRun(XWPFDocument docxDocument, XWPFParagraph paragraph, Element e, List<String> allStyles) {
		// 父标签 style中的样式
		List<String> parentStyle = new ArrayList<>(Arrays.asList(e.attr("style") == null ? new String[0] : e.attr("style").split(";")));
		allStyles.addAll(parentStyle);
		// 父标签样式 只针对于h1、h2、h3、p
		if (e.tagName().contains("h") || "p".equals(e.tagName())) {
			allStyles.add(e.tagName() + ":");
		}
		// 父标签下的所有的子标签
		List<Node> nodes = e.childNodes();
		if (nodes != null && nodes.size() != 0) {
			// 单独处理表格
			if ("table".equals(e.tagName())) {
				// 在word中创建表格
				XWPFTable table = docxDocument.createTable();
				// 设置表格宽度
				CTTblWidth width = table.getCTTbl().addNewTblPr().addNewTblW();
				width.setType(STTblWidth.DXA);
				width.setW(BigInteger.valueOf(9072));
				nodes = nodes.stream().filter(x -> x instanceof Element).collect(Collectors.toList());
				// 获取tbody
				Element tableBody = (Element) nodes.get(0);
				// 在word中创建表格的时候 会默认创建一行
				table.removeRow(0);
				// 遍历tr 行
				tableBody.childNodes().stream().filter(x -> x instanceof Element).map(x -> (Element) x).forEach(x -> {
					// 创建行
					XWPFTableRow row = table.createRow();
					// 列的下标
					AtomicInteger i = new AtomicInteger();
					// 遍历td 列
					x.childNodes().stream().filter(c -> c instanceof Element).map(c -> (Element) c).forEach(c -> {
						i.getAndIncrement();
						// 这个创建列  多行只要创建了第一行的列  下面所有的行都会有对应创建的列  所有如果多行列是一样的 只需要创建一次就可以
						XWPFTableCell cell = row.getCell(i.intValue() - 1);
						if (cell == null) {
							cell = row.createCell();
						}
						// 单元格内容垂直居中
						CTTcPr tcpr = cell.getCTTc().addNewTcPr();
						CTVerticalJc va = tcpr.addNewVAlign();
						va.setVal(STVerticalJc.CENTER);
						// 暂存当前标签的样式
						List<String> tempStyles = new ArrayList<>();
						// 获取样式
						tempStyles = cellStyle(c, tempStyles);
						if (tempStyles.size() != 0) {
							allStyles.addAll(tempStyles);
							XWPFRun run = cell.addParagraph().createRun();
							run.setText(c.text());
							setFontStyle(allStyles, run, paragraph, docxDocument, c);
							allStyles.removeAll(allStyles);
						} else {
							cell.setText(c.text());
						}
					});
				});
				return;
			}
			for (Node node : nodes) {
				// 创建段落内容
				XWPFRun run = paragraph.createRun();
				// 当子标签 为TextNode 的时候 说明这个子标签仅仅是文本 没有被标签修饰
				if (node instanceof TextNode) {
					TextNode textNode = (TextNode) node;
					run.setText(textNode.text().replaceAll(" ", ""));
					// 设置样式 已父标签样式为基准
					setFontStyle(allStyles, run, paragraph, docxDocument, null);
					// 当子标签 为Element的时候 说明这个子标签不单单是文本，还包括标签  这时候需要根据对应标签的样式 修饰当前文本
				} else if (node instanceof Element) {
					Element children = (Element) node;
					// 存放子标签中style样式
					List<String> childrenStyle = new ArrayList<>(Arrays.asList(children.attr("style") == null ? new String[0] : children.attr("style").split(";")));
					// 子标签名称
					String tagName = children.tagName();
					// 添加标签对应的样式
					childrenStyle.add(addTagStyle(tagName, children));
					// 将父子标签样式汇总
					allStyles.addAll(childrenStyle);
					// 查看子标签下是否还有子标签
					List<Node> grandsons = children.childNodes().stream().filter(x -> x instanceof Element).collect(Collectors.toList());
					if (grandsons != null && grandsons.size() != 0) {
						// 如果子标签下还有字标签  样式需要包括当前子标签的样式
						createXWPFRun(docxDocument, paragraph, children, allStyles);
					}
					// 查看当前标签 有没有包括内容  如果不加这个直接text()方法 他会获取当前标签的子标签的内容  造成内容重复的问题
					List<Node> childrenNodes = children.childNodes().stream().filter(x -> x instanceof TextNode).collect(Collectors.toList());
					if (childrenNodes != null && childrenNodes.size() != 0) {
						// 子标签内容
						String text = children.text();
						// 这里设置内容需要排除超链接a标签  a标签需要特殊设置内容
						List<String> aStyle = allStyles.stream().filter(x -> x.indexOf("a:") >= 0).collect(Collectors.toList());
						if (aStyle == null || aStyle.size() == 0) {
							run.setText(text.replaceAll(" ", ""));
						}
						setFontStyle(allStyles, run, paragraph, docxDocument, children);
					}
					// 去除子标签样式 当前标签的内容 仅限于当前标签  下个标签不能使用
					allStyles.removeAll(childrenStyle);
				}
			}
		} else {
			// 横线 hr标签
			if ("hr".equals(e.tagName())) {
				// 创建段落内容
				XWPFRun run = paragraph.createRun();
				run.setText("———————————————————————————————————————");
			}
		}
	}

	/**
	 * 添加图片
	 *
	 * @param run
	 * @param pictureUrl
	 * @param fileName
	 * @return
	 */
	public static XWPFRun addPicture(XWPFRun run, String pictureUrl, String fileName) {
		if (pictureUrl == null) {
			return run;
		}
		URL url = null;
		InputStream inputStream = null;
		try {
			pictureUrl = URLDecoder.decode(pictureUrl, "UTF-8");
			url = new URL(pictureUrl);
			inputStream = url.openConnection().getInputStream();
			// 获取 图片类型 并添加图片
			run.addPicture(inputStream, getPictureType(pictureUrl), fileName, Units.toEMU(400), Units.toEMU(256));
		} catch (MalformedURLException e) {
			throw new RuntimeException("图片url解析异常,url=" + pictureUrl);
		} catch (IOException e) {
			throw new RuntimeException("获取图片异常,url=" + pictureUrl);
		} catch (InvalidFormatException e) {
			throw new RuntimeException("添加图片异常,url=" + pictureUrl);
		}

		return run;
	}

	/**
	 * 根据图片类型，取得对应的图片类型代码
	 *
	 * @param picType
	 * @return int
	 */
	private static int getPictureType(String picType) {
		int res = XWPFDocument.PICTURE_TYPE_PICT;
		if (picType != null) {
			if (picType.equalsIgnoreCase("png")) {
				res = XWPFDocument.PICTURE_TYPE_PNG;
			} else if (picType.equalsIgnoreCase("dib")) {
				res = XWPFDocument.PICTURE_TYPE_DIB;
			} else if (picType.equalsIgnoreCase("emf")) {
				res = XWPFDocument.PICTURE_TYPE_EMF;
			} else if (picType.equalsIgnoreCase("jpg") || picType.equalsIgnoreCase("jpeg")) {
				res = XWPFDocument.PICTURE_TYPE_JPEG;
			} else if (picType.equalsIgnoreCase("wmf")) {
				res = XWPFDocument.PICTURE_TYPE_WMF;
			}
		}
		return res;
	}

	/**
	 * 循环取出表格中的字体样式
	 *
	 * @param c          cell单元格
	 * @param tempStyles 样式
	 * @return
	 */
	private static List<String> cellStyle(Element c, List<String> tempStyles) {
		List<Element> collect = c.childNodes().stream().filter(s -> s instanceof Element).map(s -> (Element) s).collect(Collectors.toList());
		for (Element element : collect) {
			tempStyles.add(addTagStyle(element.tagName(), element));
			List<Node> childs = element.childNodes().stream().filter(s -> s instanceof Element).collect(Collectors.toList());
			if (childs != null && childs.size() != 0) {
				cellStyle(element, tempStyles);
			}
		}
		return tempStyles;
	}

	/**
	 * 添加标签样式
	 *
	 * @param tagName  标签
	 * @param children 元素
	 * @return
	 */
	public static String addTagStyle(String tagName, Element children) {
		String style = "";
		switch (tagName) {
			// 字体相关
			case "font":
				// 字体
				if (StringUtils.isNotBlank(children.attr("face"))) {
					style = "face:" + children.attr("face");
				}
				// 大小
				if (StringUtils.isNotBlank(children.attr("size"))) {
					style = "size:" + children.attr("size");
				}
				// 颜色
				if (StringUtils.isNotBlank(children.attr("color"))) {
					style = "color:" + children.attr("color");
				}
				break;
			// 删除线
			case "strike":
				style = "strike:";
				break;
			// br标签
			case "br":
				style = "br:";
				break;
			// u标签 下划线
			case "u":
				style = "u:";
				break;
			// i标签 斜体
			case "i":
				style = "i:";
				break;
			// b标签 加粗
			case "b":
				style = "b:";
				break;
			// a 标签 超链接
			case "a":
				if (StringUtils.isNotBlank(children.attr("href"))) {
					style = "a:" + children.attr("href");
				}
				break;
		}
		return style;
	}

	/**
	 * 设置内容样式
	 *
	 * @param styles       样式
	 * @param run          需要赋予样式的对象 XWPFRun
	 * @param paragraph    段落
	 * @param docxDocument 目标文档
	 */
	public static void setFontStyle(List<String> styles, XWPFRun run, XWPFParagraph paragraph, XWPFDocument docxDocument, Element children) {
		// 自定义样式排序 父标签样式在前 子标签样式在后  子标签样式可以覆盖父标签样式
		Collections.sort(styles, new Comparator<String>() {
			@Override
			public int compare(String o1, String o2) {
				if (o1.contains("h") || "p".equals(o1)) {
					return -1;
				} else if ("p".equals(o2) || o2.contains("h")) {
					return 1;
				} else {
					return 0;
				}
			}
		});
		for (String styleValue : styles) {
			if (StringUtils.isBlank(styleValue)) {
				continue;
			}
			// 获取style中的样式
			String style = styleValue.substring(0, styleValue.indexOf(":")).replaceAll(" ", "");
			// style样式对应的值
			String value = styleValue.substring(styleValue.indexOf(":") + 1).replaceAll(" ", "");
			switch (style) {
				/*--------------------------------标签对应的样式 例如p 、h1、h2、h3*/
				// 正文
				case "p":
					//对齐方式
					//paragraph.setAlignment(ParagraphAlignment.BOTH);
					//首行缩进：567==1厘米
					//paragraph.setIndentationFirstLine(567);
					break;
				// h1标题
				case "h1":
					addCustomHeadingStyle(docxDocument, "标题 1", 1);
					paragraph.setStyle("标题 1");
					run.setBold(true);
					run.setColor("000000");
					run.setFontFamily("宋体");
					run.setFontSize(20);
					break;
				// h2 标题
				case "h2":
					addCustomHeadingStyle(docxDocument, "标题 2", 2);
					paragraph.setStyle("标题 2");
					run.setBold(true);
					run.setColor("000000");
					run.setFontFamily("宋体");
					run.setFontSize(18);
					break;
				// h3标题
				case "h3":
					addCustomHeadingStyle(docxDocument, "标题 3", 3);
					paragraph.setStyle("标题 3");
					run.setBold(true);
					run.setColor("000000");
					run.setFontFamily("宋体");
					run.setFontSize(16);
					break;
				// 行高 行间距
				case "line-height":
					run.setTextPosition(Integer.parseInt(value));
					break;
                /* 左内边距  因为单位是em  暂不处理此标签
                case "padding-left":
                    CTSectPr sectPr = docxDocument.getDocument().getBody().addNewSectPr();
                    CTPageMar pageMar = sectPr.addNewPgMar();
                    pageMar.setLeft(BigInteger.valueOf(720L));
                    break;*/
				// 字体
				case "face":
					CTFonts ctFonts = run.getCTR().addNewRPr().addNewRFonts();
					// 设置中文字体
					ctFonts.setEastAsia(value);
					// 设置英文数字字体
					ctFonts.setAscii(value);
					break;
				// 大小
				case "size":
					run.setFontSize(fontSizeConvert(value));
					break;
				// 颜色
				case "color":
					run.setColor(value.replaceAll("#", ""));
					break;
				// 删除线
				case "strike":
					run.setStrikeThrough(true);
					break;
				// br 换行
				case "br":
					run.addCarriageReturn();
					break;
				// u 下划线
				case "u":
					run.setUnderline(UnderlinePatterns.SINGLE);
					break;
				// i 斜体
				case "i":
					run.setItalic(true);
					break;
				// b 加粗
				case "b":
					run.setBold(true);
					break;
				// background-color 背景色
				case "background-color":
					run.getCTR().addNewRPr().addNewHighlight().setVal(getBackground(value));
					break;
				// a 超连接
				case "a":
					XWPFParagraphWrapper wrapper = new XWPFParagraphWrapper(paragraph);
					XWPFRun hyperRun = wrapper.insertNewHyperLinkRun(0, value);
					hyperRun.setText(children.text().replaceAll(" ", ""));
					hyperRun.setColor("0563C1");
					hyperRun.setUnderline(UnderlinePatterns.SINGLE);
					break;
				// 文本对齐方式
				case "text-align":
					if ("center".equals(value)) {
						paragraph.setAlignment(ParagraphAlignment.CENTER);
					} else if ("left".equals(value)) {
						paragraph.setAlignment(ParagraphAlignment.LEFT);
					} else if ("right".equals(value)) {
						paragraph.setAlignment(ParagraphAlignment.RIGHT);
					}
					break;
			}
		}
	}

	/**
	 * 增加自定义标题样式。这里用的是stackoverflow的源码
	 *
	 * @param docxDocument 目标文档
	 * @param strStyleId   样式名称
	 * @param headingLevel 样式级别
	 */
	private static void addCustomHeadingStyle(XWPFDocument docxDocument, String strStyleId, int headingLevel) {

		CTStyle ctStyle = CTStyle.Factory.newInstance();
		ctStyle.setStyleId(strStyleId);

		CTString styleName = CTString.Factory.newInstance();
		styleName.setVal(strStyleId);
		ctStyle.setName(styleName);

		CTDecimalNumber indentNumber = CTDecimalNumber.Factory.newInstance();
		indentNumber.setVal(BigInteger.valueOf(headingLevel));

		// lower number > style is more prominent in the formats bar
		ctStyle.setUiPriority(indentNumber);

		CTOnOff onoffnull = CTOnOff.Factory.newInstance();
		ctStyle.setUnhideWhenUsed(onoffnull);

		// style shows up in the formats bar
		ctStyle.setQFormat(onoffnull);

		// style defines a heading of the given level
		CTPPr ppr = CTPPr.Factory.newInstance();
		ppr.setOutlineLvl(indentNumber);
		ctStyle.setPPr(ppr);

		XWPFStyle style = new XWPFStyle(ctStyle);

		// is a null op if already defined
		XWPFStyles styles = docxDocument.createStyles();

		style.setType(STStyleType.PARAGRAPH);
		styles.addStyle(style);

	}

	/**
	 * 字体像素大小转换为word字体大小
	 *
	 * @param level
	 * @return
	 */
	public static Integer fontSizeConvert(String level) {
		Integer fontSize = null;
		if (StringUtils.isBlank(level)) {
			return fontSize;
		}
		switch (level) {
			// 对应像素大小 10px
			case "1":
				fontSize = 7;
				break;
			// 对应像素大小 13px
			case "2":
				fontSize = 8;
				break;
			// 对应像素大小 16px
			case "3":
				fontSize = 9;
				break;
			// 对应像素大小 18px
			case "4":
				fontSize = 10;
				break;
			// 对应像素大小 24px
			case "5":
				fontSize = 14;
				break;
			// 对应像素大小 32px
			case "6":
				fontSize = 18;
				break;
			// 对应像素大小 48px
			case "7":
				fontSize = 28;
				break;
			case "8":
				fontSize = 36;
				break;
			case "9":
				fontSize = 48;
				break;
			case "10":
				fontSize = 72;
				break;
			default:
				fontSize = 5;
		}
		return fontSize;
	}

	/**
	 * 17 种标准色是 aqua, black, blue, fuchsia, gray, green, lime, maroon, navy, olive, orange, purple, red, silver, teal, white, yellow。
	 *
	 * @param color
	 * @return
	 * @date 2020年4月7日 下午7:16:39
	 */
	public static STHighlightColor.Enum getBackground(String color) {
		color = color.replaceAll(" ", "");
		if ("yellow".equals(color) || "rgb(255,255,0)".equals(color) || "#FFFF00".equals(color)) {
			//1-黄色
			return STHighlightColor.YELLOW;
		} else if ("lime".equals(color) || "rgb(0,255,0)".equals(color) || "#00FF00".equals(color)) {
			//2-绿色
			return STHighlightColor.GREEN;
		} else if ("aqua".equals(color) || "rgb(0,255,255)".equals(color) || "#00FFFF".equals(color)) {
			//3-青色
			return STHighlightColor.CYAN;
		} else if ("fuchsia".equals(color) || "rgb(255,0,255)".equals(color) || "#FF00FF".equals(color)) {
			//4-粉红色
			return STHighlightColor.MAGENTA;
		} else if ("blue".equals(color) || "rgb(0,0,255)".equals(color) || "#0000FF".equals(color)) {
			//5-蓝色
			return STHighlightColor.BLUE;
		} else if ("red".equals(color) || "rgb(255,0,0)".equals(color) || "#FF0000".equals(color)) {
			//6-红色
			return STHighlightColor.RED;
		} else if ("navy".equals(color) || "rgb(0,0,128)".equals(color) || "#000080".equals(color)) {
			//7-深蓝色
			return STHighlightColor.DARK_BLUE;
		} else if ("teal".equals(color) || "rgb(0,128,128)".equals(color) || "#008080".equals(color)) {
			//8-深青色
			return STHighlightColor.DARK_CYAN;
		} else if ("green".equals(color) || "rgb(0,128,0)".equals(color) || "#008000".equals(color)) {
			//9-深绿色
			return STHighlightColor.DARK_GREEN;
		} else if ("purple".equals(color) || "rgb(128,0,128)".equals(color) || "#800080".equals(color)) {
			//10-深粉红色，紫色
			return STHighlightColor.DARK_MAGENTA;
		} else if ("maroon".equals(color) || "rgb(128,0,0)".equals(color) || "#800000".equals(color)) {
			//11-深红色
			return STHighlightColor.DARK_RED;
		} else if ("olive".equals(color) || "rgb(128,128,0)".equals(color) || "#808000".equals(color)) {
			//12-深黄色
			return STHighlightColor.DARK_YELLOW;
		} else if ("gray".equals(color) || "rgb(128,128,128)".equals(color) || "#808080".equals(color)) {
			//13-深灰色
			return STHighlightColor.DARK_GRAY;
		} else if ("silver".equals(color) || "rgb(192,192,192)".equals(color) || "#C0C0C0".equals(color)) {
			//14-浅灰色
			return STHighlightColor.LIGHT_GRAY;
		} else if ("black".equals(color) || "rgb(0,0,0)".equals(color) || "#000000".equals(color)) {
			//15-黑色
			return STHighlightColor.BLACK;
		} else {
			//无色
			return STHighlightColor.NONE;
		}
	}
}

